package com.alibaba.algo.excellistener;

import com.alibaba.algo.domain.user.SysEmployeeDomain;
import com.alibaba.algo.entity.user.SysEmployee;
import com.alibaba.algo.enums.EmployeeHeadEnum;
import com.alibaba.algo.util.StringUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * 有个很重要的点 DemoDataListener 不能被spring管理，
 * 要每次读取excel都要new,然后里面用到spring可以构造方法传进去
 *
 * @author : WangZhiPan
 * @date : 2021/11/3 11:20
 */
public class SysEmployeeListener extends AnalysisEventListener<SysEmployee> {
    private static final Logger log = LoggerFactory.getLogger(SysEmployeeListener.class);

    /**
     * 每隔5条存储数据库，实际使用中可以3000条，然后清理list ，方便内存回收
     */
    private static final int BATCH_COUNT = 3000;
    /**
     * 缓存的数据
     */
    private List<SysEmployee> list = new ArrayList<>(BATCH_COUNT);
    /**
     * 解析异常数据
     */
    private List<Integer> exceptionList = new ArrayList<>(BATCH_COUNT);
    /**
     * 已经存在的employeeCodes
     */
    private List<Integer> noEmployeeCodes = new ArrayList<>(BATCH_COUNT);
    private List<Integer> noOrgNames = new ArrayList<>(BATCH_COUNT);
    private List<Integer> orgOrPositionNotValid = new ArrayList<>(BATCH_COUNT);
    private List<Integer> orgNotExists = new ArrayList<>(BATCH_COUNT);
    private List<Integer> wrongMobiles = new ArrayList<>(BATCH_COUNT);
    private List<Integer> notDigitEmployeeCodes = new ArrayList<>(BATCH_COUNT);
    ;;


    /**
     * 成功数量
     */
    private Integer successNum = 0;

    /**
     * 假设这个是一个DAO，当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private SysEmployeeDomain employeeDomain;


    public SysEmployeeListener() {
        this.employeeDomain = new SysEmployeeDomain();
    }

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param employeeDomain
     */
    public SysEmployeeListener(SysEmployeeDomain employeeDomain) {
        this.employeeDomain = employeeDomain;
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        log.info("headMap={}", JSON.toJSONString(headMap));
        Collection<String> values = headMap.values();
        if (!values.contains(EmployeeHeadEnum.EMPLOYEE_CODE.getName())) {
            throw new RuntimeException("缺少工号字段，请上传正确的文件。");
        }
        if (!values.contains(EmployeeHeadEnum.ORG_NAME.getName())) {
            throw new RuntimeException("缺少组织名称字段，请上传正确的文件。");
        }
        if (!values.contains(EmployeeHeadEnum.MOBILE_PHONE.getName())) {
            throw new RuntimeException("缺少手机号码字段，请上传正确的文件。");
        }
    }


    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(SysEmployee data, AnalysisContext context) {
        //本身有重复数据
        //数据库已有数据
        //数据不合法
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        if (isValid(data, context)) {
            list.add(data);
            // 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM
            if (list.size() >= BATCH_COUNT) {
                saveData();
                // 存储完成清理 list
                list = new ArrayList<>(BATCH_COUNT);
            }
        }

    }

    private boolean isValid(SysEmployee data, AnalysisContext context) {
        String orgName = data.getOrgName();
        Integer rowIndex = context.readRowHolder().getRowIndex();
        //过滤解析出来的空行
        if (data.isEmpty()) {
            return false;
        }
        //校验员工工号是否为空
        if (StringUtil.isEmpty(data.getEmployeeCode())) {
            noEmployeeCodes.add(rowIndex);
            return false;
        }

        if (!StringUtil.isDigit(data.getEmployeeCode())) {
            notDigitEmployeeCodes.add(rowIndex);
            return false;
        }

        if (StringUtil.isEmpty(data.getMobilePhone()) || !StringUtil.isTelephoneRough(data.getMobilePhone())) {
            wrongMobiles.add(rowIndex);
            return false;
        }

        //校验组织名称是否为空
        if (StringUtil.isEmpty(orgName)) {
            noOrgNames.add(rowIndex);
            return false;
        }
        //组织是否存在
        if (!queryOrgId(data)) {
            orgNotExists.add(rowIndex);
            return false;
        }
        //岗位不为空时，岗位是否存在
        if (!StringUtil.isEmpty(data.getPositionName())) {
            if (!queryOrgAndPosition(data)) {
                orgOrPositionNotValid.add(rowIndex);
                return false;
            }
        }
        return true;
    }

    private boolean queryOrgAndPosition(SysEmployee data) {
        return employeeDomain.qryPosition(data);
    }

    private boolean queryOrgId(SysEmployee data) {
        return employeeDomain.isOrgNameExist(data);
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据，确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成！");
    }


    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        Integer currentRowNum = context.readRowHolder().getRowIndex();
        log.error("current row num=={},error=={}", currentRowNum, exception);
        if (currentRowNum == 0) {
            super.onException(exception, context);
        }
        exceptionList.add(currentRowNum);
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据，开始存储数据库！", list.size());
        employeeDomain.saveOrUpdateBatch(list);
        this.successNum += list.size();
        log.info("存储数据库成功！");
    }


    /**
     * 生成提示消息
     *
     * @return
     */
    public String makeMsg() {
        String msg = String.format("导入成功%d条。\n", this.successNum);
        if (!this.noEmployeeCodes.isEmpty()) {
            String noEmployeeCodeMsgNum = String.format("员工工号为空数据%d条。\n", this.noEmployeeCodes.size());
            String noEmployeeCodeMsg = String.format("员工工号为空数据:%s\n", this.noEmployeeCodes.toString());
            msg += noEmployeeCodeMsgNum + noEmployeeCodeMsg;
        }
        if (!this.noOrgNames.isEmpty()) {
            String noOrgCodeMsgNum = String.format("组织名称为空数据%d条。\n", this.noOrgNames.size());
            String noOrgCodeMsg = String.format("组织名称为空数据:%s\n", this.noOrgNames.toString());
            msg += noOrgCodeMsgNum + noOrgCodeMsg;
        }
        if (!this.orgNotExists.isEmpty()) {
            String orgNotExistsNum = String.format("组织名称不存在数据%d条。\n", this.orgNotExists.size());
            String orgNotExistsMsg = String.format("组织名称不存在数据:%s\n", this.orgNotExists.toString());
            msg += orgNotExistsNum + orgNotExistsMsg;
        }

        if (!this.orgOrPositionNotValid.isEmpty()) {
            String noPositionNum = String.format("岗位和组织不匹配%d条\n", this.orgOrPositionNotValid.size());
            String noPositionMsg = String.format("岗位和组织不匹配数据:%s\n", this.orgOrPositionNotValid.toString());
            msg += noPositionNum + noPositionMsg;
        }
        if (!this.exceptionList.isEmpty()) {
            String exceptionMsgNum = String.format("解析异常数据%d条。\n", this.exceptionList.size());
            String exceptionMsg = String.format("解析异常数据:%s\n", this.exceptionList.toString());
            msg += exceptionMsgNum + exceptionMsg;
        }
        if (!this.wrongMobiles.isEmpty()) {
            String wrongMobilesNum = String.format("手机号码异常%d条。\n", this.wrongMobiles.size());
            String wrongMobilesMsg = String.format("手机号码异常数据:%s\n", this.wrongMobiles.toString());
            msg += wrongMobilesNum + wrongMobilesMsg;
        }
        if (!this.notDigitEmployeeCodes.isEmpty()) {
            String notDigitEmployeeCodesNum = String.format("员工工号异常(只允许纯数字)%d条。\n", this.notDigitEmployeeCodes.size());
            String notDigitEmployeeCodesMsg = String.format("员工工号异常数据:%s\n", this.notDigitEmployeeCodes.toString());
            msg += notDigitEmployeeCodesNum + notDigitEmployeeCodesMsg;
        }
        return msg;
    }
}
